﻿/*
 * This file is part of CommunitySettlers.
 *
 * CommunitySettlers is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * CommunitySettlers is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with CommunitySettlers.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author: Christoph Husse
 * 
 * Also checkout our homepage: http://communitysettlers.codeplex.com/
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace SettlersEngine
{
    public delegate void DChangeHandler<in TObject, in TValue>(TObject inSender, TValue inOldValue, TValue inNewValue);
    public delegate void DNotifyHandler<in TObject>(TObject inSender);
    public delegate void DNotifyHandler<in TObject, in TArg1>(TObject inSender, TArg1 inArg1);

    internal interface IPositionTracker
    {
        System.Drawing.Point GetTrackedPosition();
        event DChangeHandler<IPositionTracker, System.Drawing.Point> OnTrackedPositionChanged;
    }

    internal class TopologicalList<TValue> where TValue : IPositionTracker
    {
        private List<List<List<TValue>>> m_Topology = new List<List<List<TValue>>>();

        public Int32 Granularity { get; private set; }
        public Int32 Width { get; private set; }
        public Int32 Height { get; private set; }

        public TopologicalList(int inGranularity, int inWidth, int inHeight)
        {
            Granularity = inGranularity;
            Width = inWidth / inGranularity + 1;
            Height = inHeight / inGranularity + 1;

            for (int x = 0; x < Width; x++)
            {
                List<List<TValue>> column = new List<List<TValue>>();

                m_Topology.Add(column);

                for (int y = 0; y < Height; y++)
                {
                    column.Add(new List<TValue>());
                }
            }
        }

        public WalkResult EnumAround(Point inAround, Func<TValue, WalkResult> inHandler)
        {
            return GridSearch.GridWalkAround(new Point(inAround.X / Granularity, inAround.Y / Granularity), Width, Height, (position) => {

                foreach (var entry in m_Topology[position.X][position.Y])
                {
                    if (inHandler(entry) == WalkResult.Success)
                        return WalkResult.Success;
                }

                return WalkResult.NotFound;
            });
        }        

        protected List<TValue> OpenList(Point inPosition)
        {
            int granX = inPosition.X / Granularity;
            int granY = inPosition.Y / Granularity;

            return m_Topology[granX][granY];
        }

        public void Add(TValue inValue)
        {
            List<TValue> list = OpenList(inValue.GetTrackedPosition());

            if (list.Contains(inValue))
                throw new InvalidOperationException("Value has already been added.");

            inValue.OnTrackedPositionChanged += OnPositionChanged;
            list.Add(inValue);
        }

        private void OnPositionChanged(IPositionTracker inTrackable, Point inOldPosition, Point inNewPosition)
        {
            List<TValue> oldList = OpenList(inOldPosition);
            List<TValue> newList = OpenList(inNewPosition);

            if (oldList == newList)
                return;

            if (!oldList.Contains((TValue)inTrackable))
                throw new ApplicationException();

            if (newList.Contains((TValue)inTrackable))
                throw new ApplicationException();

            oldList.Remove((TValue)inTrackable);
            newList.Add((TValue)inTrackable);
        }

        public void Remove(TValue inValue)
        {
            List<TValue> list = OpenList(inValue.GetTrackedPosition());

            inValue.OnTrackedPositionChanged -= OnPositionChanged;

            if (!list.Remove(inValue))
                throw new ApplicationException();
        }
    }
}
